#include <stx/imp/cmn.hpp>

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wambiguous-reversed-operator"
#pragma clang diagnostic ignored "-Wsign-conversion"
#include <stx/ext/simdjson.cpp>
#pragma clang diagnostic pop

inline namespace stx
{
/*
Notes on unusual behavior of JsonObjectIterator__ & JsonArrayIterator__
-----------------------------------------------------------------------

Both of JsonObjectIterator__ & JsonArrayIterator__ always assumes pos2 is the 
last pos. That means:

`pos == pos` is true iff pos is at the last position.
`pos != pos` is true iff pos is not at the last position.
*/

template<IsNonVoid Iterator_>
requires IsIdentical<Iterator_, JsonObjectIterator__> ||
    IsIdentical<Iterator_, JsonArrayIterator__>
[[nodiscard]] constexpr bool isLastPos__(Iterator_ const pos) noexcept
{
    return pos == pos;
}

JsonValue::JsonValue(JsonValue__ const& value) : opt_value_(value)
{}

bool JsonValue::isNull() const
{
    return !this->opt_value_ ||
           this->opt_value_->type() == JsonValueType__::null;
}

bool JsonValue::isBool() const
{
    return this->opt_value_ &&
           this->opt_value_->type() == JsonValueType__::boolean;
}

bool JsonValue::isNumber() const
{
    return this->opt_value_ &&
           this->opt_value_->type() == JsonValueType__::number;
}

bool JsonValue::isInt64() const
{
    if (!this->opt_value_)
    {
        return false;
    }

    if (this->opt_value_->type() == JsonValueType__::number)
    {
        auto const n = JsonNumber__(this->opt_value_->get_number());
        return n.get_number_type() == JsonNumberType__::signed_integer ||
               n.get_number_type() == JsonNumberType__::unsigned_integer;
    }

    return false;
}

bool JsonValue::isDouble() const
{
    if (!this->opt_value_)
    {
        return false;
    }

    if (this->opt_value_->type() == JsonValueType__::number)
    {
        auto const n = JsonNumber__(this->opt_value_->get_number());
        return n.get_number_type() == JsonNumberType__::floating_point_number;
    }

    return false;
}

bool JsonValue::isString() const
{
    return this->opt_value_ &&
           this->opt_value_->type() == JsonValueType__::string;
}

bool JsonValue::isObject() const
{
    return this->opt_value_ &&
           this->opt_value_->type() == JsonValueType__::object;
}

bool JsonValue::isArray() const
{
    return this->opt_value_ &&
           this->opt_value_->type() == JsonValueType__::array;
}

bool JsonValue::getBool() const
{
    ASSERT(this->opt_value_);
    return static_cast<bool>(this->opt_value_->get_bool());
}

Number JsonValue::getNumber() const
{
    ASSERT(this->opt_value_);

    if (this->isInt64())
    {
        return this->getInt64();
    }
    else if (this->isDouble())
    {
        return this->getDouble();
    }

    RUNTIME_ERROR("Invalid json value type."sv);
}

std::int64_t JsonValue::getInt64() const
{
    ASSERT(this->opt_value_);
    if (this->opt_value_->type() == JsonValueType__::number)
    {
        auto const n = JsonNumber__(this->opt_value_->get_number());
        if (n.get_number_type() == JsonNumberType__::signed_integer)
        {
            return this->opt_value_->get_int64();
        }

        if (n.get_number_type() == JsonNumberType__::unsigned_integer)
        {
            return Number(
                static_cast<std::uint64_t>(this->opt_value_->get_uint64()));
        }
    }

    RUNTIME_ERROR("Invalid json value type."sv);
}

double JsonValue::getDouble() const
{
    return static_cast<double>(this->opt_value_->get_double());
}

std::string_view JsonValue::getString() const
{
    return static_cast<std::string_view>(this->opt_value_->get_string());
}

[[nodiscard]] JsonType JsonValue::getType() const
{
    if (!this->opt_value_)
    {
        return JsonType::K_NULL;
    }

    switch (this->opt_value_->type())
    {
    case JsonValueType__::null:
        return JsonType::K_NULL;

    case JsonValueType__::boolean:
        return JsonType::K_BOOL;

    case JsonValueType__::number:
    {
        auto const n = JsonNumber__(this->opt_value_->get_number());
        if (n.get_number_type() == JsonNumberType__::signed_integer ||
            n.get_number_type() == JsonNumberType__::unsigned_integer)
        {
            return JsonType::K_INT64;
        }
        else if (n.get_number_type() == JsonNumberType__::floating_point_number)

        {
            return JsonType::K_DOUBLE;
        }

        break;
    }

    case JsonValueType__::string:
        return JsonType::K_STRING;

    case JsonValueType__::object:
        return JsonType::K_OBJECT;

    case JsonValueType__::array:
        return JsonType::K_ARRAY;
    }

    RUNTIME_ERROR("Unknown json value type."sv);
}

std::optional<JsonValue> JsonValue::getValue(std::string_view const jp) const
{
    if (!this->opt_value_)
    {
        return std::nullopt;
    }

    return JsonValue(JsonValue__(this->opt_value_->at_pointer(jp)));
}

JsonValue__ const& JsonValue::get() const
{
    return *this->opt_value_;
}

JsonObjectIterator::JsonObjectIterator(JsonObjectIterator__ const& pos)
    : opt_ctx_(Context_{.pos = pos})
{
    this->setField_();
}

bool JsonObjectIterator::operator==(JsonObjectIterator const&) const
{
    return !this->opt_ctx_ || isLastPos__(this->opt_ctx_->pos);
}

JsonObjectIterator::value_type const& JsonObjectIterator::operator*() const
{
    CHECK(this->opt_ctx_);
    CHECK(!isLastPos__(this->opt_ctx_->pos));

    return this->opt_ctx_->field;
}

JsonObjectIterator::value_type const* JsonObjectIterator::operator->() const
{
    CHECK(this->opt_ctx_);
    CHECK(!isLastPos__(this->opt_ctx_->pos));

    return &this->opt_ctx_->field;
}

JsonObjectIterator& JsonObjectIterator::operator++()
{
    CHECK(this->opt_ctx_);
    CHECK(!isLastPos__(this->opt_ctx_->pos));

    ++this->opt_ctx_->pos;
    this->setField_();

    return *this;
}

JsonObjectIterator JsonObjectIterator::operator++(int)
{
    CHECK(this->opt_ctx_);
    CHECK(!isLastPos__(this->opt_ctx_->pos));

    auto old = *this;
    ++*this;

    return old;
}

void JsonObjectIterator::setField_()
{
    if (!isLastPos__(this->opt_ctx_->pos))
    {
        auto field                  = JsonField__(*this->opt_ctx_->pos);
        this->opt_ctx_->field.key   = field.unescaped_key();
        this->opt_ctx_->field.value = JsonValue(field.value());
    }
}

JsonObject::JsonObject(JsonObject__ const& obj) : opt_obj_(obj)
{}

JsonObjectIterator JsonObject::begin() const
{
    if (!this->opt_obj_)
    {
        return {};
    }

    return JsonObjectIterator(JsonObjectIterator__(this->opt_obj_->begin()));
}

JsonObjectIterator JsonObject::end() const
{
    return {};
}

std::optional<JsonValue>
JsonObject::operator[](std::string_view const key) const
{
    if (!this->opt_obj_)
    {
        return std::nullopt;
    }

    auto value = JsonValue__{};
    auto ec    = (*this->opt_obj_)[key].get(value);

    if (ec != simdjson::SUCCESS)
    {
        LOG_ERROR("Failed to look up key: "sv, key, _I_, ec);
        return std::nullopt;
    }

    return JsonValue(value);
}

std::optional<JsonValue> JsonObject::getValue(std::string_view const jp) const
{
    if (!this->opt_obj_)
    {
        return std::nullopt;
    }

    auto       value = JsonValue__{};
    auto const ec    = this->opt_obj_->at_pointer(jp).get(value);

    if (ec != simdjson::SUCCESS)
    {
        LOG_ERROR("Failed to look up json path: "sv, jp, _I_, ec);
        return std::nullopt;
    }

    return JsonValue(value);
}

JsonArrayIterator::JsonArrayIterator(JsonArrayIterator__ const& pos)
    : opt_ctx_(Context_{.pos = pos})
{
    this->setField_();
}

bool JsonArrayIterator::operator==(JsonArrayIterator const&) const
{
    return !this->opt_ctx_ || isLastPos__(this->opt_ctx_->pos);
}

JsonArrayIterator::value_type const& JsonArrayIterator::operator*() const
{
    CHECK(this->opt_ctx_);
    CHECK(!isLastPos__(this->opt_ctx_->pos));

    return this->opt_ctx_->value;
}

JsonArrayIterator::value_type const* JsonArrayIterator::operator->() const
{
    CHECK(this->opt_ctx_);
    CHECK(!isLastPos__(this->opt_ctx_->pos));

    return &this->opt_ctx_->value;
}

JsonArrayIterator& JsonArrayIterator::operator++()
{
    CHECK(this->opt_ctx_);
    CHECK(!isLastPos__(this->opt_ctx_->pos));

    ++this->opt_ctx_->pos;
    this->setField_();

    return *this;
}

JsonArrayIterator JsonArrayIterator::operator++(int)
{
    CHECK(this->opt_ctx_);
    CHECK(!isLastPos__(this->opt_ctx_->pos));

    auto old = *this;
    ++*this;

    return old;
}

void JsonArrayIterator::setField_()
{
    if (!isLastPos__(this->opt_ctx_->pos))
    {
        this->opt_ctx_->value = JsonValue(JsonValue__(*this->opt_ctx_->pos));
    }
}

JsonArray::JsonArray(JsonArray__ const& arr) : opt_arr_(arr)
{}

JsonArrayIterator JsonArray::begin() const
{
    if (!this->opt_arr_)
    {
        return {};
    }

    return JsonArrayIterator(JsonArrayIterator__(this->opt_arr_->begin()));
}

JsonArrayIterator JsonArray::end() const
{
    return {};
}

std::optional<JsonValue> JsonArray::operator[](std::size_t const idx) const
{
    if (!this->opt_arr_)
    {
        return std::nullopt;
    }

    auto value = JsonValue__{};
    auto ec    = this->opt_arr_->at(idx).get(value);

    if (ec != simdjson::SUCCESS)
    {
        LOG_ERROR("Failed to look up index: "sv, idx, _I_, ec);
        return std::nullopt;
    }

    return JsonValue(value);
}

std::optional<JsonValue> JsonArray::getValue(std::string_view const jp) const
{
    if (!this->opt_arr_)
    {
        return std::nullopt;
    }

    auto       value = JsonValue__{};
    auto const ec    = this->opt_arr_->at_pointer(jp).get(value);

    if (ec != simdjson::SUCCESS)
    {
        LOG_ERROR("Failed to look up json path: "sv, jp, _I_, ec);
        return std::nullopt;
    }

    return JsonValue(value);
}

JsonObject getJsonObject(JsonValue const& value)
{
    ASSERT(value.isObject());
    return JsonObject(const_cast<JsonValue__&>(value.get()).get_object());
}

JsonArray getJsonArray(JsonValue const& value)
{
    ASSERT(value.isArray());
    return JsonArray(const_cast<JsonValue__&>(value.get()).get_array());
}

JsonParser::JsonParser() : JsonParser(0)
{}

JsonParser::JsonParser(std::size_t const init_capacity)
{
    if (init_capacity > 0)
    {
        this->raw_.reserve(init_capacity);
    }
}

bool JsonParser::parse(std::string_view const sv_jsn)
{
    try
    {
        auto const min_capacity = sv_jsn.size() + C_SIMDJSON_PADDING_SIZE;
        if (this->raw_.capacity() < min_capacity)
        {
            this->raw_.reserve(min_capacity);
        }

        ASSERT(this->raw_.capacity() >= min_capacity,
               this->raw_.capacity(),
               min_capacity);
        resizeDefaultInit(this->raw_, sv_jsn.size());
        ASSERT(this->raw_.size() == sv_jsn.size(),
               this->raw_.size(),
               sv_jsn.size());

        std::memcpy(this->raw_.data(), sv_jsn.data(), sv_jsn.size());
        return this->parse(
            this->raw_.data(), this->raw_.size(), this->raw_.capacity());
    }
    catch (std::exception const& e)
    {
        LOG_ERROR(e.what());
    }

    return false;
}

bool JsonParser::parse(void const* const data,
                       std::size_t const size,
                       std::size_t const capacity)
{
    try
    {
        ASSERT(data);
        ASSERT(size + C_SIMDJSON_PADDING_SIZE <= capacity, size, capacity);

        this->doc_ = this->parser_.iterate(
            static_cast<char const*>(data), size, capacity);
        return true;
    }
    catch (std::exception const& e)
    {
        LOG_ERROR(e.what());
    }

    return false;
}

std::optional<JsonValue> JsonParser::getValue(std::string_view const jp) const
{
    auto       value = JsonValue__{};
    auto const ec =
        const_cast<JsonDocument__&>(this->doc_).at_pointer(jp).get(value);
    if (ec != simdjson::SUCCESS)
    {
        return std::nullopt;
    }

    return JsonValue(value);
}

bool isValidUtf8String(std::string_view const sv)
{
    return simdjson::validate_utf8(sv.data(), sv.size());
}

bool isValidJsonString(std::string_view const sv)
{
    try
    {
        auto parser = simdjson::dom::parser{};

        auto fn = [](auto fn1, auto const& jb)
        {
            switch (jb.type())
            {
            case simdjson::dom::element_type::INT64:
            case simdjson::dom::element_type::UINT64:
            case simdjson::dom::element_type::DOUBLE:
            case simdjson::dom::element_type::STRING:
            case simdjson::dom::element_type::BOOL:
            case simdjson::dom::element_type::NULL_VALUE:
                return true;

            case simdjson::dom::element_type::ARRAY:
            {
                for (auto const& sub_jb : jb)
                {
                    if (!fn1(fn1, sub_jb))
                    {
                        return false;
                    }
                }

                return true;
            }

            case simdjson::dom::element_type::OBJECT:
            {
                for (auto const& field : simdjson::dom::object(jb))
                {
                    if (field.value.is_object() || field.value.is_array())
                    {
                        if (!fn1(fn1, field.value))
                        {
                            return false;
                        }
                    }
                }

                return true;
            }

            default:
                return false;
            }
        };

        return fn(fn, parser.parse(sv));
    }
    catch (std::exception const& e)
    {
        LOG_ERROR(e.what());
    }

    return false;
}

JsonBuilderNode::Value_::Value_()
    : up_value_(std::make_unique<JsonBuilderNode>())
{}

JsonBuilderNode::Value_::Value_(std::unique_ptr<JsonBuilderNode> up_value)
    : up_value_(std::move(up_value))
{}

JsonBuilderNode::Value_::Value_(Value_ const& other)
    : up_value_(std::make_unique<JsonBuilderNode>(*other.up_value_))
{}

JsonBuilderNode const& JsonBuilderNode::Value_::get() const
{
    return *this->up_value_;
}

JsonBuilderNode& JsonBuilderNode::Value_::get()
{
    return *this->up_value_;
}

JsonBuilderNode::JsonBuilderNode(std::nullptr_t const) noexcept
    : JsonBuilderNode{}
{}

JsonBuilderNode::JsonBuilderNode(bool const b) noexcept : data_(b)
{}

JsonBuilderNode::JsonBuilderNode(std::string s) noexcept : data_(std::move(s))
{}

JsonBuilderNode::JsonBuilderNode(JsonValue const& jsn_value) : JsonBuilderNode{}
{
    if (jsn_value.isNull())
    {
        return;
    }

    if (jsn_value.isBool())
    {
        this->data_ = jsn_value.getBool();
        return;
    }

    if (jsn_value.isInt64())
    {
        this->data_ = jsn_value.getInt64();
        return;
    }

    if (jsn_value.isDouble())
    {
        this->data_ = jsn_value.getDouble();
        return;
    }

    if (jsn_value.isString())
    {
        this->data_ = tos(jsn_value.getString());
        return;
    }

    if (jsn_value.isObject())
    {
        this->data_ =
            std::move(JsonBuilderNode(getJsonObject(jsn_value)).data_);
        return;
    }

    if (jsn_value.isArray())
    {
        this->data_ = std::move(JsonBuilderNode(getJsonArray(jsn_value)).data_);
        return;
    }

    RUNTIME_ERROR("Invalid json value type."sv);
}

JsonBuilderNode::JsonBuilderNode(JsonObject const& jsn_obj) : data_(Object_{})
{
    for (auto const& [key, value] : jsn_obj)
    {
        (*this)[key] = value;
    }
}

JsonBuilderNode::JsonBuilderNode(JsonArray const& jsn_arr) : data_(Array_{})
{
    for (auto const& value : jsn_arr)
    {
        this->append(JsonBuilderNode(value));
    }
}

void JsonBuilderNode::erase(std::string_view const sv_key)
{
    if (std::holds_alternative<Object_>(this->data_))
    {
        eraseAll(std::get<Object_>(this->data_), sv_key);
    }
}

std::optional<JsonBuilderNode::Value_>
JsonBuilderNode::extract(std::string_view const sv_key)
{
    if (!std::holds_alternative<Object_>(this->data_))
    {
        return std::nullopt;
    }

    auto&      obj = std::get<Object_>(this->data_);
    auto const pos = obj.find(sv_key);
    if (pos != obj.end())
    {
        ON_SCOPE_EXIT(obj.erase(pos));
        return std::move(pos->second);
    }

    return std::nullopt;
}

JsonBuilderNode& JsonBuilderNode::operator[](std::string s_key)
{
    if (!std::holds_alternative<Object_>(this->data_))
    {
        this->data_ = Object_{};
    }

    auto&      obj = std::get<Object_>(this->data_);
    auto const pos = obj.find(s_key);
    if (pos == obj.end())
    {
        return obj[std::move(s_key)].get();
    }

    return pos->second.get();
}

JsonBuilderNode& JsonBuilderNode::operator[](std::string_view const sv_key)
{
    if (!std::holds_alternative<Object_>(this->data_))
    {
        this->data_ = Object_{};
    }

    auto&      obj = std::get<Object_>(this->data_);
    auto const pos = obj.find(sv_key);
    if (pos == obj.end())
    {
        return obj[tos(sv_key)].get();
    }

    return pos->second.get();
}

JsonBuilderNode& JsonBuilderNode::append(JsonBuilderNode elem)
{
    if (!std::holds_alternative<Array_>(this->data_))
    {
        this->data_ = Array_{};
    }

    insert(std::get<Array_>(this->data_),
           std::make_unique<JsonBuilderNode>(std::move(elem)));
    return *this;
}

std::string JsonBuilderNode::dump() const
{
    gLastError().clear();

    try
    {
        auto const fn_escape = [](std::string_view const sv)
        {
            auto const double_quote_cnt =
                Number(ranges::count_if(sv, '"' == _1));

            auto s_dst = std::string{};
            resizeDefaultInit(s_dst, double_quote_cnt + sv.size() + 2);

            auto i     = 0uz;
            s_dst[i++] = '"';

            for (auto const c : sv)
            {
                if ('"' == c)
                {
                    s_dst[i++] = '\\';
                }

                s_dst[i++] = c;
            }

            s_dst[i++] = '"';
            ASSERT(s_dst.size() == i);

            return s_dst;
        };

        auto const fn_dump_obj = [=, this]
        {
            auto tmp_ss = std::deque<std::string>{};
            insert(tmp_ss, "{"s);

            for (auto const& [s_key, value] : std::get<Object_>(this->data_))
            {
                insert(
                    tmp_ss,
                    scat(fn_escape(s_key), ":"sv, value.get().dump(), ","sv));
            }

            if (tmp_ss.back().back() == ',')
            {
                tmp_ss.back().pop_back();
            }

            insert(tmp_ss, "}"s);
            return mcat2<std::string>(tmp_ss);
        };

        auto const fn_dump_array = [=, this]
        {
            auto tmp_ss = std::deque<std::string>{};
            insert(tmp_ss, "["s);

            for (auto const& value : std::get<Array_>(this->data_))
            {
                insert(tmp_ss, scat(value.get().dump(), ","sv));
            }

            if (tmp_ss.back().back() == ',')
            {
                tmp_ss.back().pop_back();
            }

            insert(tmp_ss, "]"s);
            return mcat2<std::string>(tmp_ss);
        };

        switch (this->data_.index())
        {
        case 0:
        {
            static_assert(IsIdentical<std::monostate,
                                      decltype(std::get<0>(this->data_))>);
            return "null"s;
        }
        case 1:
        {
            static_assert(
                IsIdentical<bool, decltype(std::get<1>(this->data_))>);
            return tos(std::get<bool>(this->data_));
        }
        case 2:
        {
            static_assert(
                IsIdentical<std::int64_t, decltype(std::get<2>(this->data_))>);
            return tos(std::get<std::int64_t>(this->data_));
        }
        case 3:
        {
            static_assert(
                IsIdentical<double, decltype(std::get<3>(this->data_))>);
            auto s = tos(std::get<double>(this->data_));
            if (isMatch(s, rgx<R"(^(0|[1-9][0-9]*)\.(0|[0-9]+[1-9])$)">()))
            {
                return s;
            }

            if (isMatch(s, rgx<R"(^(0|[1-9][0-9]*)\.0+$)">()))
            {
                return regexReplace(
                    s, rgx<R"(^(0|[1-9][0-9]*)\.0+$)">(), R"($1.0)"_sv);
            }

            return s | views::trim_back('0' == _1) | ranges::to<std::string>;
        }
        case 4:
        {
            static_assert(
                IsIdentical<std::string, decltype(std::get<4>(this->data_))>);
            return fn_escape(std::get<std::string>(this->data_));
        }
        case 5:
        {
            static_assert(
                IsIdentical<Object_, decltype(std::get<5>(this->data_))>);
            return fn_dump_obj();
        }
        case 6:
        {
            static_assert(
                IsIdentical<Array_, decltype(std::get<6>(this->data_))>);
            return fn_dump_array();
        }
        default:
            RUNTIME_ERROR("Invalid JsonValueType: "sv, this->data_.index());
        }
    }
    catch (std::exception const& e)
    {
        LOG_ERROR(e.what());
    }

    return {};
}
} // namespace stx